<?php

namespace PhpYes\Com;

use PhpYes\Modules\HTTP\Header\ContentType;
use PhpYes\Modules\HTTP\Header\RequestHeader;

class WgxRequest
{
    private $scheme;
    private $host;
    private $port;
    private $path;
    private $method;
    private $queryParameters = -1;
    private $bodyParameters = -1;

    /**
     * @return string - https|http
     */
    public function getScheme()
    {
        if (empty($this->scheme)) {
            if (
                (isset($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']))
                || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
            ) {
                $this->scheme = 'https';
            } else {
                $this->scheme = 'http';
            }
        }

        return $this->scheme;
    }

    public function getUserInfo()
    {
        // todo
    }

    /**
     * @return string
     * @throws \Exception
     */
    public function getHost()
    {
        if (empty($this->host)) {
            if (!empty($_SERVER['HTTP_HOST'])) {
                $rawHost = $_SERVER['HTTP_HOST'];
                $host = '';
            } elseif (!empty($_SERVER['SERVER_NAME'])) {
                $rawHost = $_SERVER['SERVER_NAME'];
                $host = '';
            } elseif (!empty($_SERVER['SERVER_ADDR'])) {
                $rawHost = $_SERVER['SERVER_ADDR'];
                $host = '';
            } else {
                $rawHost = '';
                $host = 'localhost';
            }

            if ($rawHost) {
                // trim and remove port number from host
                // host is lowercase as per RFC 952/2181

                // $t = strtolower(preg_replace('/:\d+$/', '', trim($rawHost)));

                // if ($rawHost[0] === '[') {
                //     $pos = strpos($rawHost, ':', strrpos($rawHost, ']'));
                // } else {
                $pos = strrpos($rawHost, ':'); // 先只认为 : 后跟 端口号 直至结尾, 如果 在以后的实践中遇到了特殊情况, 修改这里兼容一下就好了
                // }

                if (false === $pos) {
                    $t = trim($rawHost);
                } else {
                    $t = trim(substr($rawHost, 0, $pos));
                }
                if ($t) {
                    $host = strtolower($t);
                } else {
                    throw new \Exception("Invalid Host: {$rawHost}");
                }

                // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
                // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
                // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names

                // 正则还是比较耗性能, 先注释掉下面的校验代码, 如果 在以后的实践中发现确实需要下面的校验逻辑, 就解开下面校验代码的注释
                // if ('' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
                //     throw new \Exception("Invalid Host: {$rawHost}");
                // }
            }

            $this->host = $host;
        }

        return $this->host;
    }

    /**
     * @return int
     */
    public function getPort()
    {
        if (empty($this->port)) {
            if (!empty($_SERVER['HTTP_HOST'])) { // 从 HTTP_HOST 取 port, 为空 就根据 scheme 取默认值
                $rawHost = $_SERVER['HTTP_HOST'];
                $pos = strrpos($rawHost, ':');
                if (false === $pos) {
                    $this->port = $this->getScheme() === 'https' ? 443 : 80;
                } else {
                    $this->port = intval(substr($rawHost, $pos + 1));
                }
            } elseif (!empty($_SERVER['SERVER_PORT'])) { // 从 SERVER_PORT 取 port, 为空 就根据 scheme 取默认值
                $this->port = intval($_SERVER['SERVER_PORT']);
            } else {
                $this->port = $this->getScheme() === 'https' ? 443 : 80;
            }
        }

        return $this->port;
    }

    /**
     * if(port == defaultPort('https' ? 443 : 80)) : host
     * else : host:port
     * @return string
     * @throws \Exception
     */
    public function getHostPort()
    {
        $host = $this->getHost();
        $port = $this->getPort();
        $defaultPort = $this->getScheme() === 'https' ? 443 : 80;
        if ($defaultPort == $port) {
            return $host;
        } else {
            return "{$host}:{$port}";
        }
    }

    /**
     * if https {
     *   if port == 443 : https://host
     *   else : https://host:port
     * } else {
     *   if port == 80 : http://host
     *   else : http://host:port
     * }
     * @return string
     * @throws \Exception
     */
    public function getOrigin()
    {
        $hostPort = $this->getHostPort();
        return $this->getScheme() === 'https' ? "https://{$hostPort}" : "http://{$hostPort}";
    }

    /**
     * @return string
     * @throws \Exception
     */
    public function getPath()
    {
        if (empty($this->path)) {
            $requestUri = '';
            if (!empty($_SERVER['HTTP_X_ORIGINAL_URL'])) {
                // IIS with Microsoft Rewrite Module
                $requestUri = $_SERVER['HTTP_X_ORIGINAL_URL'];
            } elseif (!empty($_SERVER['HTTP_X_REWRITE_URL'])) {
                // IIS with ISAPI_Rewrite
                $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
            } elseif (!empty($_SERVER['REQUEST_URI'])) {
                $requestUri = $_SERVER['REQUEST_URI'];
            } elseif (!empty($_SERVER['ORIG_PATH_INFO'])) {
                // IIS 5.0, PHP as CGI
                $requestUri = $_SERVER['ORIG_PATH_INFO'];
            }

            if (empty($requestUri)) {
                $this->path = '/';
                return $this->path;
            }

            // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
            $scheme = $this->getScheme();
            if ($scheme == 'https') {
                if (strncmp($requestUri, 'https://', 8) === 0) {
                    $requestUri = substr($requestUri, 8);
                }
            } else {
                if (strncmp($requestUri, 'http://', 7) === 0) {
                    $requestUri = substr($requestUri, 7);
                }
            }
            if (empty($requestUri)) {
                $this->path = '/';
                return $this->path;
            }

            $slashPos = strpos($requestUri, '/');
            if ($slashPos === false) {
                $this->path = '/';
                return $this->path;
            }
            $qstPos = strpos($requestUri, '?');
            if ($qstPos === false) {
                $this->path = substr($requestUri, $slashPos);
            } elseif ($qstPos > $slashPos) {
                $this->path = substr($requestUri, $slashPos, $qstPos - $slashPos);
            } else {
                $this->path = '/';
            }
        }

        return $this->path;
    }

    /**
     * @return string
     */
    public function getQuery()
    {
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        if (empty($this->method)) {
            $this->method = empty($_SERVER['REQUEST_METHOD']) ? 'GET' : strtoupper(trim($_SERVER['REQUEST_METHOD']));
            if ($this->method === 'POST' && !empty($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
                $this->method = strtoupper(trim($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']));
            }
        }

        return $this->method;
    }

    /**
     * @return array|null
     */
    public function getQueryParams()
    {
        if ($this->queryParameters === -1) {
            $this->queryParameters = null;
            if ($_GET) {
                $this->queryParameters = $_GET;
            } else {
                $this->queryParameters = array();
                $query = $this->getQuery();
                if ($query) {
                    $pairArr = explode('&', $query);
                    foreach ($pairArr as $pair) {
                        if (empty($pair)) {
                            continue;
                        }
                        $kv = explode('=', $pair);
                        if (count($kv) > 1) {
                            $this->queryParameters[trim($kv[0])] = trim($kv[1]);
                        } else {
                            $this->queryParameters[trim($kv[0])] = '';
                        }
                    }
                }
            }
        }

        return $this->queryParameters;
    }

    /**
     * @return array|mixed|null|string
     */
    public function getBodyParams()
    {
        if ($this->bodyParameters === -1) {
            $this->bodyParameters = null;
            if ($_POST) {
                $this->bodyParameters = $_POST;
            } else {
                $raw = file_get_contents('php://input');
                if ($raw) {
                    $ct = ContentType::getRequestContentType() ? ContentType::getRequestContentType()->getTypeSubType() : '';
                    if ($ct == 'application/json') {
                        $this->bodyParameters = json_decode($raw, true);
                    } elseif ($ct == 'application/x-www-form-urlencoded') {
                        $arr = explode('&', $raw);
                        $this->bodyParameters = array();
                        foreach ($arr as $item) {
                            $pair = explode('=', $item);
                            if (count($pair) < 2) {
                                continue;
                            }
                            $this->bodyParameters[urldecode($pair[0])] = urldecode($pair[1]);
                        }
                    } else {
                        $this->bodyParameters = $raw;
                    }
                }
            }
        }

        return $this->bodyParameters;
    }

    /**
     * @return bool
     */
    public function isXMLHttpRequest()
    {
        $XRequestedWith = RequestHeader::getHeader('X-Requested-With');
        if (empty($XRequestedWith)) {
            return false;
        }
        $XRequestedWith = trim($XRequestedWith);

        return $XRequestedWith == 'XMLHttpRequest';
    }

}